24. PowerShell Remoting¶
Note
The below information is extensively based in information taken from the PowerShell® Notes for Professionals book. I plan to extend this information based on my day to day usage of the language.
24.1: Connecting to a Remote Server via PowerShell¶
Using credentials from your local computer:
1 | Enter-PSSession 192.168.1.1 |
Prompting for credentials on the remote computer
1 | Enter-PSSession 192.168.1.1 -Credential $( Get-Credential ) |
24.2: Run commands on a Remote Computer¶
Once Powershell remoting is enabled (Enable-PSRemoting) You can run commands on the remote computer like this:
1 2 3 | Invoke-Command -ComputerName "RemoteComputerName" - ScriptBlock { Write host "Remote Computer Name: $ENV:ComputerName" } |
The above method creates a temporary session and closes it right after the command or scriptblock ends.
To leave the session open and run other command in it later, you need to create a remote session first:
1 | $Session = New-PSSession -ComputerName "RemoteComputerName" |
Then you can use this session each time you invoke commands on the remote computer:
1 2 3 | Invoke-Command -Session $Session - ScriptBlock { Write host "Remote Computer Name: $ENV:ComputerName" } |
1 2 3 | Invoke-Command -Session $Session - ScriptBlock { Get-Date } |
If you need to use different Credentials, you can add them with the -Credential Parameter:
1 2 | $Cred = Get-Credential Invoke-Command -Session $Session -Credential $Cred - ScriptBlock {...} |
Remoting serialization warning
Note
It is important to know that remoting serializes PowerShell objects on the remote system and deserializes them on your end of the remoting session, i.e. they are converted to XML during transport and lose all of their methods.
1 2 3 | $output = Invoke-Command -Session $Session - ScriptBlock { Get-WmiObject -Class win32_printer } |
1 | $output | Get-Member -MemberType Method |
1 | TypeName: Deserialized.System.Management.ManagementObject#root\cimv2\Win32_Printer |
1 2 3 4 | Name MemberType Definition ---- ---------- ---------- GetType Method type GetType() ToString Method string ToString(), string ToString(string format, System.IFormatProvi... |
Whereas you have the methods on the regular PS object:
1 | Get-WmiObject -Class win32_printer | Get-Member -MemberType Method |
1 | TypeName: System.Management.ManagementObject#root\cimv2\Win32_Printer |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Name MemberType Definition CancelAllJobs Method System.Management.ManagementBaseObject CancelAllJobs() GetSecurityDescriptor Method System.Management.ManagementBaseObject GetSecurityDescriptor() Pause Method System.Management.ManagementBaseObject Pause() PrintTestPage Method System.Management.ManagementBaseObject PrintTestPage() RenamePrinter Method System.Management.ManagementBaseObject RenamePrinter(System.String NewPrinterName) Reset Method System.Management.ManagementBaseObject Reset() Resume Method System.Management.ManagementBaseObject Resume() SetDefaultPrinter Method System.Management.ManagementBaseObject SetDefaultPrinter() SetPowerState Method System.Management.ManagementBaseObject SetPowerState(System.UInt16 PowerState, System.String Time) SetSecurityDescriptor Method System.Management.ManagementBaseObject SetSecurityDescriptor (System.Management.ManagementObject#Win32_SecurityDescriptor Descriptor) |
Argument Usage
To use arguments as parameters for the remote scripting block, one might either use the ArgumentList parameter of Invoke-Command, or use the $Using: syntax.
Using ArgumentList with unnamed parameters (i.e. in the order they are passed to the scriptblock):
1 2 3 4 5 6 7 | $servicesToShow = "service1" $fileName = "C:\temp\servicestatus.csv" Invoke-Command -Session $session -ArgumentList $servicesToShow,$fileName - ScriptBlock { Write-Host "Calling script block remotely with $($Args.Count)" Get-Service -Name $args[ 0 ] Remove-Item -Path $args[ 1 ] -ErrorAction SilentlyContinue -Force } |
Using ArgumentList with named parameters:
1 2 3 4 5 6 7 8 9 | $servicesToShow = "service1" $fileName = "C:\temp\servicestatus.csv" Invoke-Command -Session $session -ArgumentList $servicesToShow,$fileName - ScriptBlock { Param($serviceToShowInRemoteSession,$fileToDelete) Write-Host "Calling script block remotely with $($Args.Count)" Get-Service -Name $serviceToShowInRemoteSession Remove-Item -Path $fileToDelete -ErrorAction SilentlyContinue -Force } |
Using $Using: syntax:
1 2 3 4 5 6 | $servicesToShow = "service1" $fileName = "C:\temp\servicestatus.csv" Invoke-Command -Session $session - ScriptBlock { Get-Service $Using:servicesToShow Remove-Item -Path $fileName -ErrorAction SilentlyContinue -Force } |
24.3: Enabling PowerShell Remoting¶
PowerShell remoting must first be enabled on the server to which you wish to remotely connect.
1 | Enable-PSRemoting -Force |
This command does the following:
1 2 3 4 5 6 7 8 9 10 11 | Runs the Set-WSManQuickConfig cmdlet, which performs the following tasks: - Starts the WinRM service. - Sets the startup type on the WinRM service to Automatic. - Creates a listener to accept requests on any IP address, if one does not already exist. - Enables a firewall exception for WS-Management communications. - Registers the Microsoft.PowerShell and Microsoft.PowerShell.Workflow session configurations, if it they are not already registered. - Registers the Microsoft.PowerShell32 session configuration on 64-bit computers, if it is not already registered. - Enables all session configurations. - Changes the security descriptor of all session configurations to allow remote access. - Restarts the WinRM service to make the preceding changes effective. |
Only for non-domain environments
For servers in an AD Domain the PS remoting authentication is done through Kerberos ('Default'), or NTLM ('Negotiate'). If you want to allow remoting to a non-domain server you have two options. Either set up WSMan communication over HTTPS (which requires certificate generation) or enable basic authentication which sends your credentials across the wire base64-encoded (that's basically the same as plain-text so be careful with this).
In either case you'll have to add the remote systems to your WSMan trusted hosts list.
Enabling Basic Authentication
1 | Set-Item WSMan:\localhost\Service\AllowUnencrypted $true |
Then on the computer you wish to connect from , you must tell it to trust the computer you're connecting to.
1 2 3 4 5 | Set-Item WSMan:\localhost\Client\TrustedHosts '192.168.1.1,192.168.1.2' Set-Item WSMan:\localhost\Client\TrustedHosts *.contoso.com Set-Item WSMan:\localhost\Client\TrustedHosts * |
Important : You must tell your client to trust the computer addressed in the way you want to connect (e.g. if you connect via IP, it must trust the IP not the hostname)
24.4: A best practise for automatically cleaning-up PSSessions¶
When a remote session is created via the New-PSsession cmdlet, the PSSession persists until the current PowerShell session ends. Meaning that, by default, the PSSession and all associated resources will continue to be used until the current PowerShell session ends.
Multiple active PSSessions can become a strain on resources, particularly for long running or interlinked scripts that create hundreds of PSSessions in a single PowerShell session.
It is best practise to explicitly remove each PSSession after it is finished being used. [1]
The following code template utilises try-catch-finally in order to achieve the above, combining error handling with a secure way to ensure all created PSSessions are removed when they are finished being used:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | try { $session = New-PSsession -Computername "RemoteMachineName" Invoke-Command -Session $session - ScriptBlock { write-host "This is running on $ENV:ComputerName"} } catch { Write-Output "ERROR: $_" } finally { if ($session) { Remove-PSSession $session } } |
References: [1]
https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/new-pssession